#include "xwindowmanager.h"
#include <QDebug>
#include <QProcess>
#include <QTimer>



#ifndef _WIN32
#include <QX11Info>
#include <X11/extensions/shape.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>

#include "stdlib.h"
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"

#endif


XWindowManager::XWindowManager(QObject* parent)
    : QObject{parent}
{

}


#ifndef _WIN32
static WindowList qxt_getWindows(Atom prop)
{
    WindowList res;
    Atom type = 0;
    int format = 0;
    uchar* data = 0;
    ulong count, after;
    Display* display = QX11Info::display();
    Window window = QX11Info::appRootWindow();
    if (XGetWindowProperty(display, window, prop, 0, 1024 * sizeof(Window) / 4, False, AnyPropertyType,
                           &type, &format, &count, &after, &data) == Success)
    {
        Window* list = reinterpret_cast<Window*>(data);
        for (uint i = 0; i < count; ++i)
            res.append(list[i]);
        if (data)
            XFree(data);
    }
    return res;
}

WindowList XWindowManager::windows()
{
    static Atom net_clients = 0;
    if (!net_clients)
        net_clients = XInternAtom(QX11Info::display(), "_NET_CLIENT_LIST_STACKING", True);

    return qxt_getWindows(net_clients);
}

QString XWindowManager::windowTitle(WId window)
{
    QString name;
    char* str = 0;
    if (XFetchName(QX11Info::display(), window, &str))
        name = QString::fromLatin1(str);
    if (str)
        XFree(str);
    return name;
}

QStringList XWindowManager::windowTitles()
{
    WindowList windows = XWindowManager::windows();
    QStringList titles;
    Q_FOREACH(WId window, windows)
    titles += XWindowManager::windowTitle(window);
    return titles;
}

WId XWindowManager::activeWindow()
{
    static Atom net_active = 0;
    if (!net_active)
        net_active = XInternAtom(QX11Info::display(), "_NET_ACTIVE_WINDOW", True);

    return qxt_getWindows(net_active).value(0);
}

WId XWindowManager::findWindow(const QString& title)
{
    Window result = 0;
    WindowList list = windows();
    Q_FOREACH (const Window &wid, list)
    {
        if (windowTitle(wid) == title)
        {
            result = wid;
            break;
        }
    }
    return result;
}

WId XWindowManager::windowAt(const QPoint& pos)
{
    Window result = 0;
    WindowList list = windows();
    for (int i = list.size() - 1; i >= 0; --i)
    {
        WId wid = list.at(i);
        if (windowGeometry(wid).contains(pos))
        {
            result = wid;
            break;
        }
    }
    return result;
}

QRect XWindowManager::windowGeometry(WId window)
{
    int x, y;
    uint width, height, border, depth;
    Window root, child;
    Display* display = QX11Info::display();
    XGetGeometry(display, window, &root, &x, &y, &width, &height, &border, &depth);
    XTranslateCoordinates(display, window, root, x, y, &x, &y, &child);

    static Atom net_frame = 0;
    if (!net_frame)
        net_frame = XInternAtom(QX11Info::display(), "_NET_FRAME_EXTENTS", True);

    QRect rect(x, y, width, height);
    Atom type = 0;
    int format = 0;
    uchar* data = 0;
    ulong count, after;
    if (XGetWindowProperty(display, window, net_frame, 0, 4, False, AnyPropertyType,
                           &type, &format, &count, &after, &data) == Success)
    {
        // _NET_FRAME_EXTENTS, left, right, top, bottom, CARDINAL[4]/32
        if (count == 4)
        {
            long* extents = reinterpret_cast<long*>(data);
            rect.adjust(-extents[0], -extents[2], extents[1], extents[3]);
        }
        if (data)
            XFree(data);
    }
    return rect;
}

void XWindowManager::windowShow(WId wid)
{
    Display* display = QX11Info::display();
    XMapRaised(display, wid);
}
void XWindowManager::windowHide(WId wid)
{

}

//typedef struct {
//    Window  window;     /* screen saver window - may not exist */
//    int     state;      /* ScreenSaverOff, ScreenSaverOn, ScreenSaverDisabled*/
//    int     kind;       /* ScreenSaverBlanked, ...Internal, ...External */
//    unsigned long    til_or_since;   /* time til or since screen saver */
//    unsigned long    idle;      /* total time since last user input */
//    unsigned long   eventMask; /* currently selected events for this client */
//} XScreenSaverInfo;

//typedef XScreenSaverInfo* (*XScreenSaverAllocInfo)();
//typedef Status (*XScreenSaverQueryInfo)(Display* display, Drawable* drawable, XScreenSaverInfo* info);

//static XScreenSaverAllocInfo _xScreenSaverAllocInfo = 0;
//static XScreenSaverQueryInfo _xScreenSaverQueryInfo = 0;

//uint XWindowManager::idleTime()
//{
//    static bool xssResolved = false;
//    if (!xssResolved) {
//        QLibrary xssLib(QLatin1String("Xss"), 1);
//        if (xssLib.load()) {
//            _xScreenSaverAllocInfo = (XScreenSaverAllocInfo) xssLib.resolve("XScreenSaverAllocInfo");
//            _xScreenSaverQueryInfo = (XScreenSaverQueryInfo) xssLib.resolve("XScreenSaverQueryInfo");
//            xssResolved = true;
//        }
//    }

//    uint idle = 0;
//    if (xssResolved)
//    {
//        XScreenSaverInfo* info = _xScreenSaverAllocInfo();
//        const int screen = QX11Info::appScreen();
//        Qt::HANDLE rootWindow = (Qt::HANDLE) QX11Info::appRootWindow(screen);
//        _xScreenSaverQueryInfo(QX11Info::display(), (Drawable*) rootWindow, info);
//        idle = info->idle;
//        if (info)
//            XFree(info);
//    }
//    return idle;
//}



// https://tronche.com/gui/x/xlib/window-information/XInternAtom.html
// https://linux.die.net/man/3/xinternatom
// https://tronche.com/gui/x/xlib/event-handling/XSendEvent.html
// https://tronche.com/gui/x/xlib/events/structures.html
// https://tronche.com/gui/x/xlib/events/client-communication/client-message.html#XClientMessageEvent
// https://tronche.com/gui/x/xlib/window-information/properties-and-atoms.html



//unsigned long query_window_by_name(QString windowName)
//{
//    unsigned long windowId = 0;
//    auto display = XOpenDisplay(nullptr);
//    auto root_window = DefaultRootWindow(display);

////    const auto display = QX11Info::display();//Display *display = QX11Info::display();
////    const auto screen = QX11Info::appScreen();
////    Window root = QX11Info::appRootWindow(screen);//Window root = DefaultRootWindow(display);

//    Window root_return, parent_return;
//    Window * child_list = nullptr;
//    unsigned int child_num = 0;
//    XQueryTree(display, root_window, &root_return, &parent_return, &child_list, &child_num);

//    for(unsigned int i = 0; i < child_num; ++i) {
//        XTextProperty xtp_new_name;
//        if (0 != XGetWMName(display, child_list[i], & xtp_new_name))
//        {
//            qDebug() << child_list[i]<< QString(QLatin1String((const char*)xtp_new_name.value));
//            if (windowName == QString(QLatin1String((const char*)xtp_new_name.value)))
//            {
//                windowId = child_list[i];
//                qDebug() << "find window " << windowName;
//                qDebug() << "XMoveResizeWindow " << XMoveResizeWindow(display, child_list[i], 0, 0, 800, 600);
//                qDebug() << "XRaiseWindow " << XRaiseWindow(display, child_list[i]);
//                break;
//            }
//        }
//    }

//    XFree(child_list);
//    XCloseDisplay(display);
//    return windowId;
//}


//void query_all_window_property()
//{
//    auto display = XOpenDisplay(nullptr);
//    auto root_window = DefaultRootWindow(display);

////    const auto display = QX11Info::display();//Display *display = QX11Info::display();
////    const auto screen = QX11Info::appScreen();
////    Window root = QX11Info::appRootWindow(screen);//Window root = DefaultRootWindow(display);

//    Window root_return, parent_return;
//    Window * child_list = nullptr;
//    unsigned int child_num = 0;
//    XQueryTree(display, root_window, &root_return, &parent_return, &child_list, &child_num);

//    for(unsigned int i = 0; i < child_num; ++i) {
//        XWindowAttributes attrs;
//        XGetWindowAttributes(display, child_list[i], &attrs);
//        //if (attrs.map_state == 2) { // IsUnmapped, IsUnviewable, IsViewable IsViewable 即为可见窗口
//            // char* buffer = nullptr;
//            // XFetchName(display, child_list[i], &buffer);
//            // qDebug() << "#" << i <<": "<< "(" << attrs.x << ", " << attrs.y << ", " << attrs.width << "x" << attrs.height << ")" << (buffer ? buffer : "");
//            // XFree(buffer);
//            XTextProperty xtp_new_name;
//            if (0 != XGetWMName(display, child_list[i], & xtp_new_name))
//                qDebug() << child_list[i]<< QString(QLatin1String((const char*)xtp_new_name.value));
//        //}
//    }

//    XFree(child_list);
//    XCloseDisplay(display);
//}




unsigned char *getWindowProperty(Display *display, Window win, const char *prop)
{
    Atom reqAtom = XInternAtom(display, prop, true);
    if (reqAtom == None) {
        return NULL;
    }

    int retCheck = None;
    Atom retType = None;
    int retFormat = 0;
    unsigned long retItems = 0UL;
    unsigned long retMoreBytes = 0UL;
    unsigned char* retValue = NULL;

    retCheck = XGetWindowProperty(display, win, reqAtom, 0L, 0L, false, AnyPropertyType,
        &retType, &retFormat, &retItems, &retMoreBytes, &retValue);

    if (retValue != NULL) {
        XFree(retValue);
        retValue = NULL;
    }

    if (retCheck != Success || retType == None || retMoreBytes == 0) {
        return NULL;
    }

    retCheck = None;
    retFormat = 0;
    retItems = 0UL;

    // Convert the byte length into 32bit multiples.
    if (retMoreBytes % 4 != 0)
        retMoreBytes += 4 - retMoreBytes % 4;
    retMoreBytes /= 4;

    // Request the actual property value with correct length and type.
    retCheck = XGetWindowProperty(display, win, reqAtom, 0L, retMoreBytes, false, retType,
        &retType, &retFormat, &retItems, &retMoreBytes, &retValue);

    if (retCheck != Success || retMoreBytes != 0) {
        if (retValue != NULL)
            XFree(retValue);
        return NULL;
    }

    return retValue;
}

void XWindowManager::setStayOnTop(int wid, bool top)
{
    const auto display = QX11Info::display();//Display *display = QX11Info::display();

    const auto wmState = XInternAtom(display, "_NET_WM_STATE", false);
    const auto wmStateAbove = XInternAtom(display, "_NET_WM_STATE_ABOVE", false);
    const auto wmStateStaysOnTop = XInternAtom(display, "_NET_WM_STATE_STAYS_ON_TOP", false);

    XEvent xev;
    memset(&xev, 0, sizeof(xev));
    xev.xclient.type = ClientMessage;
    xev.xclient.display = display;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmState;
    xev.xclient.format = 32;
    if (top)
        xev.xclient.data.l[0] = 1;// add/set property
    else
        xev.xclient.data.l[0] = 0;// remove/unset property
    xev.xclient.data.l[1] = wmStateAbove;
    xev.xclient.data.l[2] = wmStateStaysOnTop;
    xev.xclient.data.l[3] = 1;

    XSendEvent(display,
        QX11Info::appRootWindow(QX11Info::appScreen()),
        false,
        SubstructureRedirectMask | SubstructureNotifyMask,
        &xev);

    XFlush(display);
}

void XWindowManager::showFullscreenWindow(int wid, bool fullscreen)
{
    const auto display = QX11Info::display();//Display *display = QX11Info::display();
    const auto screen = QX11Info::appScreen();

    const Atom wmState = XInternAtom(display, "_NET_WM_STATE", false);
    const Atom wmfullscreenState = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", false);

    XEvent xev;
    memset(&xev, 0, sizeof(xev));
    xev.xclient.type = ClientMessage;
    xev.xclient.display = display;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmState;
    xev.xclient.format = 32;
    if (fullscreen) {
        xev.xclient.data.l[0] = 1;//add/set property
    } else {
        xev.xclient.data.l[0] = 0;//remove/unset property
    }
    xev.xclient.data.l[1] = wmfullscreenState;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 1;

    XSendEvent(display,
               QX11Info::appRootWindow(screen),
               false,
               SubstructureRedirectMask | SubstructureNotifyMask,
               &xev
              );
    XFlush(display);
}

void XWindowManager::showMaximizedWindow(int wid, int state)
{
    // state:
    //      0: remove/unset property
    //      1: add/set property
    //      2: toggle property
    const auto display = QX11Info::display();//Display *display = QX11Info::display();
    const auto screen = QX11Info::appScreen();

    const Atom wmState = XInternAtom(display, "_NET_WM_STATE", false);
    const Atom wmAddState = XInternAtom(display, "_NET_WM_STATE_ADD", false);
    const Atom wmVMaximizedState = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", false);
    const Atom wmHMaximizedState = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", false);

    XEvent xev;
    memset(&xev, 0, sizeof(xev));
    xev.xclient.type = ClientMessage;
    xev.xclient.display = display;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmState;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = state;//xev.xclient.data.l[0] = wmAddState;
    xev.xclient.data.l[1] = wmVMaximizedState;
    xev.xclient.data.l[2] = wmHMaximizedState;
    xev.xclient.data.l[3] = 1;

    XSendEvent(display,
               QX11Info::appRootWindow(screen),
               false,
               SubstructureRedirectMask | SubstructureNotifyMask,
               &xev);
    XFlush(display);
}

void XWindowManager::showMinimizedWindow(int wid, bool minimized)
{
    const auto display = QX11Info::display();//Display *display = QX11Info::display();

    qDebug() << "display:" << display;
    const auto screen = QX11Info::appScreen();

    const Atom wmState = XInternAtom(display, "_NET_WM_STATE", false);
    const Atom wmHiddenState = XInternAtom(display, "_NET_WM_STATE_HIDDEN", false);

    XEvent xev;
    memset(&xev, 0, sizeof(xev));
    xev.xclient.type = ClientMessage;
    xev.xclient.display = display;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmState;
    xev.xclient.format = 32;
    if (minimized) {
        xev.xclient.data.l[0] = 1;//add/set property
    } else {
        xev.xclient.data.l[0] = 0;//remove/unset property
    }
    xev.xclient.data.l[1] = wmHiddenState;
    xev.xclient.data.l[2] = 0;
    xev.xclient.data.l[3] = 1;

    XSendEvent(display,
               QX11Info::appRootWindow(screen),
               false,
               SubstructureRedirectMask | SubstructureNotifyMask,
               &xev
              );
    XIconifyWindow(display, wid, screen);
    XFlush(display);
}

void XWindowManager::changeWinDesktop(int wid, bool b)
{
    unsigned long desktop = ~(0UL);

    const auto display = QX11Info::display();//Display *display = QX11Info::display();
    const auto screen = QX11Info::appScreen();
    Window root = QX11Info::appRootWindow(screen);//Window root = DefaultRootWindow(display);

    const Atom wmDesktopState = XInternAtom(display, "_NET_WM_DESKTOP", false);

    if (!b) {
        unsigned char *tmp = getWindowProperty(display, root, "_NET_CURRENT_DESKTOP");
        if (tmp == NULL) {
            qWarning("error reading current desktop property.");
        }
        else {
            desktop = *(reinterpret_cast<unsigned long*>(tmp));
            XFree(tmp);
        }
    }

    XEvent xev;
    memset(&xev, 0, sizeof(xev));
    xev.type = ClientMessage;
    xev.xclient.type = ClientMessage;
    xev.xclient.display = display;
    xev.xclient.window = wid;
    xev.xclient.message_type = wmDesktopState;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = desktop;

    XSendEvent(display,
               root,
               false,
               SubstructureRedirectMask | SubstructureNotifyMask,
               &xev
              );
    XFlush(display);
}

#endif

bool XWindowManager::getSystemFirststartup()
{
#ifndef _WIN32
    QProcess pro;
    pro.start("gsettings get org.ukui.session tycloudsysfirstboot");
    pro.waitForFinished();
    QString output = pro.readLine();
    qDebug() << "getSystemFirststartup " << output;
    return output.compare("true" ,Qt::CaseInsensitive)==0 || output.compare("true\n" ,Qt::CaseInsensitive)==0;
#else
    return false;
#endif
}
void XWindowManager::setSystemFirststartupFalse()
{
    QProcess::startDetached("gsettings set org.ukui.session tycloudsysfirstboot false");
}

void XWindowManager::exitGreeterProgram()
{
#ifndef _WIN32
//    QProcess::startDetached("echo kill > /tmp/fifogreetercs");
    int fd = open("/tmp/fifogreetercs", O_WRONLY | O_NONBLOCK);
    if (fd != -1)
    {
        write(fd, "kill", 4);
        close(fd);
    }
#endif
}
void XWindowManager::closeSystemConfig()
{
    QProcess::startDetached("killall ukui-control-center");
}
void XWindowManager::closeSystemConfigBlocked()
{
    QProcess* pro = new QProcess();
    connect(pro, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),
        [pro](int exitCode, QProcess::ExitStatus exitStatus){
            Q_UNUSED(exitCode);
            Q_UNUSED(exitStatus);
            pro->close();
            pro->kill();
            pro->deleteLater();
        });
    pro->start("killall ukui-control-center");
    pro->waitForStarted(2000);
}
void XWindowManager::showSystemConfig()
{
    QProcess::startDetached("ukui-control-center -m Homepage");
}
void XWindowManager::showSystemConfigEx()
{
    QProcess::startDetached("ukui-control-center -m Homepage LoadMode");
    //QProcess::startDetached("gnome-control-center");
}
void XWindowManager::showSystemReboot()
{
    QProcess::startDetached("ukui-session-tools");
}
void XWindowManager::showSystemConfigWifi()
{
    QProcess::startDetached("ukui-control-center -m wlanconnect");
}
void XWindowManager::showSystemConfigWifiEx()
{
    QProcess::startDetached("ukui-control-center -m wlanconnect LoadMode");
}
void XWindowManager::showSystemTaskbar()
{
    QProcess::startDetached("gsettings set org.ukui.panel.settings hidepanel false");
}
void XWindowManager::hideSystemTaskbar()
{
    QProcess::startDetached("gsettings set org.ukui.panel.settings hidepanel true");
}

void XWindowManager::enableSystemWin()
{
    QProcess::startDetached("dbus-send --session --print-reply --reply-timeout=2000 --type=method_call --dest=org.kde.kglobalaccel /kglobalaccel org.kde.KGlobalAccel.blockGlobalShortcuts boolean:false");
    QProcess::startDetached("gsettings set org.ukui.session win-key-release false");
}
void XWindowManager::disableSystemWin()
{
    // @todo timer loop kill kglobalaccel5 ?
    QTimer::singleShot(50, [](){QProcess::startDetached("killall /usr/bin/kglobalaccel5");});
    QTimer::singleShot(500, [](){QProcess::startDetached("killall /usr/bin/kglobalaccel5");});
//    QProcess::startDetached("killall /usr/bin/kglobalaccel5");
//    QProcess::startDetached("dbus-send --session --print-reply --reply-timeout=2000 --type=method_call --dest=org.kde.kglobalaccel /kglobalaccel org.kde.KGlobalAccel.blockGlobalShortcuts boolean:true");
    QProcess::startDetached("gsettings set org.ukui.session win-key-release true");
}

void XWindowManager::disableSystemWinEx()
{
    QTimer* timer = new QTimer();
    timer->setInterval(3000);
    timer->start();
    connect(timer, &QTimer::timeout, [](){ QProcess::startDetached("killall /usr/bin/kglobalaccel5"); });
    QProcess::startDetached("gsettings set org.ukui.session win-key-release true");
}


void XWindowManager::disableSystemCloseActive()
{
    QProcess::startDetached("gsettings set org.ukui.screensaver sleep-activation-enabled false");
}

void XWindowManager::disableSystemScreenSleepactive()
{
    QProcess::startDetached("gsettings set org.ukui.screensaver sleep-activation-enabled true");
}
void XWindowManager::enableSystemScreenSleepactive()
{
    QProcess::startDetached("gsettings set org.ukui.screensaver sleep-activation-enabled false");
}

void XWindowManager::windowSystemSleep()
{
    QProcess::startDetached("systemctl suspend");
}
void XWindowManager::windowSystemReboot()
{
    QProcess::startDetached("systemctl reboot");
}
void XWindowManager::windowSystemShutdown()
{
    QProcess::startDetached("systemctl poweroff");
}
